Skip to content

feat: Role based Cadence-web#1108

Merged
Assem-Uber merged 29 commits intocadence-workflow:masterfrom
ribaraka:rbac
Apr 7, 2026
Merged

feat: Role based Cadence-web#1108
Assem-Uber merged 29 commits intocadence-workflow:masterfrom
ribaraka:rbac

Conversation

@ribaraka
Copy link
Copy Markdown
Contributor

@ribaraka ribaraka commented Dec 8, 2025

Motivation: cadence-workflow/cadence#6706
Plan&Findings: cadence-workflow/cadence#7508

This PR adds JWT-based auth plumbing to Cadence Web and introduces auth-aware workflow action gating.

  • Auth strategy: JWT auth is resolved through config, with token validity/expiry, groups, admin flag, and gRPC metadata derived from the cadence-authorization cookie.
  • Dynamic config: auth behavior is routed through dynamic config resolvers: CADENCE_WEB_AUTH_STRATEGY validates auth mode, DOMAIN_ACCESS defines how domain access is determined,
  • Auth endpoints: POST /api/auth/token to set the HttpOnly cookie, DELETE /api/auth/token to clear it, GET /api/auth/me to expose public auth context.
  • API middleware now provides auth context, user info, and gRPC metadata for all route handlers that use routeHandlersDefaultMiddlewares.
  • Workflow/domain actions: start/signal/terminate/etc. are disabled with “Not authorized” when the token lacks write access;
  • Login/logout UI: navbar shows JWT paste modal when unauthenticated.

@ribaraka ribaraka force-pushed the rbac branch 2 times, most recently from 319273d to 42b3e1c Compare December 9, 2025 03:49
@ribaraka ribaraka changed the title Role based Cadence-web feat: Role based Cadence-web Dec 9, 2025
@ribaraka ribaraka force-pushed the rbac branch 3 times, most recently from 165520e to b1131fc Compare December 9, 2025 04:36
@ribaraka ribaraka force-pushed the rbac branch 3 times, most recently from 84cb2e7 to 9bd8868 Compare December 18, 2025 15:59
@ribaraka ribaraka marked this pull request as ready for review December 18, 2025 15:59
@ribaraka
Copy link
Copy Markdown
Contributor Author

start page

rbac off:
image

rbac on:
image

@ribaraka
Copy link
Copy Markdown
Contributor Author

user menu (un-auth user):
image

input to enter a token:
image

added an admin token (domains are appeared + pop message):
image

user menu (after login):
image

added a reader user:
image

reader user inside a domain:
image

added a writer user:
image

writer user inside a domain:
image

user redirected to "start page" when token is expired:
image

@ribaraka
Copy link
Copy Markdown
Contributor Author

Unauthorised error messages are shown when user access protected domain
image

@ribaraka
Copy link
Copy Markdown
Contributor Author

@Assem-Hafez @demirkayaender, when you available, please review my initial version of the feature to ship with this PR.

@Assem-Uber
Copy link
Copy Markdown
Contributor

@ribaraka Thanks for opening the PR. Is this aligned with the requirements shared here?

@ribaraka
Copy link
Copy Markdown
Contributor Author

ribaraka commented Jan 5, 2026

@Assem-Uber, yes it is 😃

@cjrolo
Copy link
Copy Markdown

cjrolo commented Jan 23, 2026

Hello, is it possible to get a review on this PR? Thanks!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive Role-Based Access Control (RBAC) support to the Cadence Web UI. The implementation aligns with Cadence backend JWT authentication by extracting tokens from cookies, forwarding them via gRPC metadata, and using JWT claims (Admin flag, groups) to control UI visibility and enable/disable actions.

Changes:

  • Authentication infrastructure with JWT cookie handling (cadence-authorization), token validation, and expiration tracking
  • Domain access control filtering domains by READ_GROUPS/WRITE_GROUPS metadata and disabling workflow actions based on write permissions
  • Login/logout UI in the navigation bar with automatic session expiration handling and token refresh support

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/utils/auth/auth-context.ts Server-side auth context resolution with JWT decoding and cookie reading
src/utils/auth/auth-shared.ts Shared auth types and domain access logic based on group membership
src/utils/auth/tests/auth-context.test.ts Comprehensive tests for auth utilities
src/app/api/auth/token/route.ts POST/DELETE endpoints for setting/clearing auth cookie
src/app/api/auth/me/route.ts GET endpoint exposing public auth context
src/utils/route-handlers-middleware/middlewares/user-info.ts Middleware to populate gRPC metadata from auth token
src/utils/route-handlers-middleware/config/route-handlers-default-middlewares.config.ts Reordered middlewares so userInfo runs before grpcClusterMethods
src/hooks/use-user-info/use-user-info.ts Client hook for fetching current auth state
src/hooks/use-domain-access/use-domain-access.ts Client hook combining user info and domain metadata to determine access
src/views/domains-page/helpers/get-all-domains.ts Filters domains list by canRead permission
src/views/domains-page/domains-page.tsx Passes auth context to domain loading
src/views/redirect-domain/redirect-domain.tsx Uses auth context for domain access checks
src/views/workflow-actions/workflow-actions.tsx Checks write permission and disables actions accordingly
src/views/workflow-actions/workflow-actions-menu/workflow-actions-menu.tsx Shows "Not authorized" for actions when write access is false
src/views/domain-page/domain-page-start-workflow-button/domain-page-start-workflow-button.tsx Disables start workflow button when write access is denied
src/views/domain-workflows/domain-workflows.tsx Conditionally fetches cluster info based on auth state
src/components/app-nav-bar/app-nav-bar.tsx Adds login/logout UI, token modal, and automatic expiration handling
src/components/auth-token-modal/auth-token-modal.tsx Modal for pasting JWT tokens
src/components/snackbar-provider/snackbar-provider.tsx Changed duration from infinite to medium
README.md Documents RBAC configuration and JWT cookie mechanism
src/config/dynamic/dynamic.config.ts Adds CADENCE_WEB_RBAC_ENABLED config

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +43
export default function AuthTokenModal({ isOpen, onClose, onSubmit }: Props) {
const [token, setToken] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

const handleSubmit = async () => {
if (!token.trim()) {
setError('Please paste a JWT token first');
return;
}

setIsSubmitting(true);
setError(null);
try {
await onSubmit(token.trim());
setToken('');
} catch (e) {
setError(
e instanceof Error ? e.message : 'Failed to save authentication token'
);
} finally {
setIsSubmitting(false);
}
};
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modal's internal state (token, error) is not reset when the modal is closed or when it's reopened. If a user encounters an error, closes the modal, and reopens it, the previous error message will still be displayed. Consider adding a useEffect to reset the state when isOpen changes to false, or call an onClose callback that resets the state.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added reset effect to prevent stale state on reopen.

@Assem-Uber
Copy link
Copy Markdown
Contributor

@ribaraka Sorry for the delay in getting back to you! I had some time off that kept me from looking into this sooner.

I’ve shared some feedback on the approach above, but please note that this feature will require a design document and discussion for broader review. Let me know today if you would like to continue that process; otherwise, I can pick it up and kick off the design documentation."

The design document should cover the following:

  • High level design for UI auth life cycle.
  • Behavior in different setups.
  • Integration with backend and responsibilities of each.
  • Security (Security measures that was taken in account)
  • Usage (How users are going to get started with different setups)

@ribaraka
Copy link
Copy Markdown
Contributor Author

Thank you for your review, @Assem-Uber!
I'd like to proceed further! I'll prepare a design doc soon.

@ribaraka ribaraka requested a review from Assem-Uber January 28, 2026 19:09
@ribaraka
Copy link
Copy Markdown
Contributor Author

@Assem-Uber, I have added the design doc, please reveiew

@ribaraka
Copy link
Copy Markdown
Contributor Author

Pasted image Pasted image (2)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/views/domains-page/helpers/get-all-domains.ts:69

  • The failedClusters logic is incorrect. At line 68, it tries to find ANY rejected result in the results array, not the specific rejection for the current cluster. This means all clusters in failedClusters will have the same rejection (the first one found by find()), rather than their own specific rejection. The logic should match each cluster config with its corresponding result by index or cluster name. For example: rejection: results[CLUSTERS_CONFIGS.indexOf(config)] (if results are in the same order) or find by matching some cluster identifier.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Stanislav Bychkov added 7 commits March 26, 2026 23:33
- Drop the useMemo for a trivial boolean derivation.
- Remove redundant Boolean(authInfo)
- Simplified the isClusterAdvancedVisibilityEnabled block
- Add the auth lifecycle hook to unload the app-nav-bar
- Drop the useMemo for a trivial boolean derivation.
- Remove redundant Boolean(authInfo)
- Simplified the isClusterAdvancedVisibilityEnabled block
- Add the lifecicle hook for auth (to unload the app-nav-bar)
- Domain group parsing is now strict to plain delimited strings (no JSON).
- Added a return type to getDomainAccessForUser
- Added isAuthenticated to BaseAuthContext, made PublicAuthContext an alias, renamed UserAuthContext to PrivatAuthContext for clearer intent.
- Simplified getPublicAuthContext to a rest-spread (token strip).
- Derived CadenceJwtClaims from Zod schema
- Removed redundant optional chaining, typeof guards.
- error on providing invalid/expired token
- Added tests for authentication route handlers.
- admins skip the domain description fetch
- stable key based on authentication state only
- removing the domain hook
- removed the explicit unauthenticated fallback
- aligning auth-token route
- fix centralize workflow action auth in dynamic config
…r non-Node runtimes

The DOMAIN_ACCESS resolver imports grpc-client which depends on
Node-only modules (fs, path). Static imports in instrumentation.ts
caused webpack to trace the entire gRPC dependency tree at compile
time, failing for non-Node compilation targets.
Signed-off-by: Stanislav Bychkov <stanislb@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
@ribaraka ribaraka requested a review from Assem-Uber March 31, 2026 14:03
return <DomainWorkflowsAdvancedGate {...props} />;
}

function DomainWorkflowsAdvancedGate(props: DomainPageTabContentProps) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: move this to separate file, we follow single component per file pattern.

} from 'baseui/modal';
import { Textarea } from 'baseui/textarea';

type Props = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: move this type in a types file

export const LOGIN_ITEM = 'login';
export const LOGOUT_ITEM = 'logout';

export const ERROR_SNACKBAR_OVERRIDES = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: overrides should be in styles file

} = useAuthLifecycle();

const logoutInFlightRef = useRef(false);
const logoutReasonRef = useRef<'manual' | 'expired' | null>(null);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: logout trigger is better naming


import useAuthLifecycle from '../use-auth-lifecycle';

type AuthResponse = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: this type should be replaced and imported from the endpoint types

userName?: string;
};

const AUTH_ENABLED: AuthResponse = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: move this to fixtures and reuse it in different tests in other files

@ribaraka
Copy link
Copy Markdown
Contributor Author

I am going to handle the TODOs and nits in another PR

@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Apr 7, 2026

Code Review ⚠️ Changes requested 7 resolved / 11 findings

Role-based access control implementation for Cadence-web with 7 commits addressing auth and domain workflows. Three critical issues remain: x-forwarded-proto header trusted without proxy validation (appears twice), useUserInfo staleTime set to 0 causing excessive refetches, and POST /api/auth/token accepting already-expired JWTs without exp claim verification.

⚠️ Security: x-forwarded-proto trusted without proxy validation

📄 src/app/api/auth/token/route.ts:22-27 📄 src/components/app-nav-bar/app-nav-bar.tsx:157 📄 src/components/app-nav-bar/app-nav-bar.tsx:164 📄 src/utils/auth/schemas/cadence-jwt-claims-schema.ts:6 📄 src/route-handlers/auth-token/helpers/get-cookie-secure-attribute.ts:3-7 📄 src/route-handlers/auth-token/set-auth-token.ts:34 📄 src/route-handlers/auth-token/clear-auth-token.ts:19

In getCookieSecureAttribute, the x-forwarded-proto header is unconditionally trusted to determine the cookie's Secure flag. If the app is deployed without a reverse proxy that strips or overwrites this header, a client can send x-forwarded-proto: http over an HTTPS connection to force secure: false, causing the auth cookie to be sent over plaintext connections. Conversely, x-forwarded-proto: https over HTTP would set Secure but the cookie would never be sent back.

Consider either: (a) documenting this as a deployment requirement, (b) adding a config option to trust/ignore the header, or (c) defaulting to secure: true in production and only relaxing for development.

⚠️ Performance: staleTime: 0 on useUserInfo causes excessive refetches

📄 src/views/shared/hooks/use-user-info/use-user-info.ts:15 📄 src/components/app-nav-bar/hooks/use-auth-lifecycle.ts:10 📄 src/views/domain-workflows/domain-workflows.tsx:23 📄 src/views/shared/hooks/use-user-info/use-user-info.ts:9

The useUserInfo hook sets staleTime: 0, which overrides the project-wide default of staleTime: Infinity (set in react-query-provider.tsx specifically to prevent immediate client-side refetches in Next.js). Because refetchOnWindowFocus defaults to true in React Query, every tab focus event triggers a new /api/auth/me request. Additionally, every component mount treats cached data as stale and refetches. Since AppNavBar (always mounted at layout level) and DomainWorkflows both consume this hook, this creates unnecessary duplicate requests on page loads.

Consider using a reasonable staleTime (e.g., 30-60 seconds) or Infinity to align with the project default. The expiry-based logout timer in AppNavBar already handles token expiration, and the explicit refetch() calls in saveToken/logout ensure freshness after mutations.

Suggested fix
// use-user-info.ts
export default function useUserInfo() {
  return useQuery<PublicAuthContext, RequestError>({
    queryKey: ['auth-me'],
    queryFn: async () => {
      const res = await request('/api/auth/me', { method: 'GET' });
      return res.json();
    },
    staleTime: 60_000, // 1 minute; mutations call refetch() explicitly
  });
}
💡 Edge Case: POST /api/auth/token sets cookie for expired JWTs

📄 src/route-handlers/auth-token/set-auth-token.ts:21 📄 src/route-handlers/auth-token/schemas/token-request-body-schema.ts:3 📄 src/route-handlers/auth-token/set-auth-token.ts:19-33 📄 src/route-handlers/auth-token/schemas/token-request-body-schema.ts:1-15

The setAuthToken handler validates the JWT format (three dot-delimited base64url segments) but does not decode or check the exp claim. An already-expired token is accepted and persisted as an HttpOnly cookie. The user gets correct feedback (the subsequent /api/auth/me call returns isValidToken: false), but the round-trip is wasted and leaves a useless cookie on the client.

Consider decoding and checking exp server-side before setting the cookie to provide immediate 400 feedback.

💡 Quality: Deprecated act import from react-dom/test-utils

📄 src/views/domain-workflows/tests/domain-workflows.test.tsx:4 📄 src/views/domain-workflows/tests/domain-workflows.test.tsx:33-46

act imported from react-dom/test-utils is deprecated in React 18 and removed in React 19. The recommended import is import { act } from '@testing-library/react' (which your test file already imports render and screen from @/test-utils/rtl, likely re-exporting @testing-library/react).

Additionally, the try/catch-around-act pattern for asserting render errors is fragile — whether the error escapes act depends on React internals and can break across versions. The previous ErrorBoundary approach was more reliable and idiomatic for testing components that throw.

Suggested fix
import { act, render, screen } from '@/test-utils/rtl';
// or restore the ErrorBoundary pattern for error assertion
✅ 7 resolved
Bug: doLogout finally-block redirects even when DELETE fails

📄 src/components/app-nav-bar/use-auth-lifecycle.ts:100-111
In doLogout, the finally block unconditionally calls refetch(), router.refresh(), router.replace('/domains'), and resets logoutInFlightRef. If the DELETE request fails (catch branch), logoutReasonRef is set to null, but the redirect still fires. This means:

  1. A failed logout still redirects the user to /domains and refreshes the page, even though they're still authenticated.
  2. If refetch() itself throws inside finally, logoutInFlightRef.current = false is never reached, permanently locking out all future logout attempts.

The finally block should only execute redirect/refresh logic on success, and the logoutInFlightRef reset should be guaranteed (e.g. in its own try/finally).

Edge Case: Authenticated users get NO_ACCESS on domains with no groups

📄 src/utils/auth/auth-shared.ts:60-62
In getDomainAccessForUser, when a domain has neither READ_GROUPS nor WRITE_GROUPS configured (both empty), any authenticated non-admin user gets NO_ACCESS. This is a deny-by-default posture which may be surprising: domains that haven't been set up for RBAC become invisible to all non-admin users when auth is enabled. If the intent is that unconfigured domains should be readable by all authenticated users, this needs a change. If it's intentional, consider adding a test case to document this contract (the existing test at auth-context.test.ts:404 covers unauthenticated users but not authenticated non-admins with empty groups).

Edge Case: POST /api/auth/token accepts already-expired JWTs

📄 src/app/api/auth/token/route.ts:49-61 📄 src/utils/auth/auth-context.ts:74-76 📄 src/components/app-nav-bar/app-nav-bar.tsx:164 📄 src/components/app-nav-bar/app-nav-bar.tsx:171
The POST handler validates JWT structure (3 base64url segments) but does not decode or check the exp claim. An expired JWT is stored in the cookie, and the user only discovers it's invalid on the next page load when resolveAuthContext silently drops the token. This could be confusing UX — the user sees { ok: true } but is immediately treated as unauthenticated.

Note: The client-side saveToken in use-auth-lifecycle.ts does refetch and check isAuthenticated after saving, showing an error snackbar. So the UX impact is limited to the API contract being misleading (returns 200 for expired tokens).

Quality: Redundant try/catch that only re-throws in handleSaveToken

📄 src/components/app-nav-bar/app-nav-bar.tsx:95 📄 src/components/app-nav-bar/app-nav-bar.tsx:106
The handleSaveToken callback wraps its body in try { ... } catch (e) { throw e; }, which has no effect — the exception propagates identically without the try/catch. This adds visual noise and may confuse readers into thinking error handling was intended but left incomplete.

Quality: Grammar issue in doc heading

📄 docs/auth-strategy-design.md:223
The heading was changed to ### One of possible Authorization Model which is grammatically incorrect. It should read ### One Possible Authorization Model or ### Example Authorization Model.

...and 2 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Role-based access control implementation for Cadence-web with 7 commits addressing auth and domain workflows. Three critical issues remain: x-forwarded-proto header trusted without proxy validation (appears twice), useUserInfo staleTime set to 0 causing excessive refetches, and POST /api/auth/token accepting already-expired JWTs without exp claim verification.

1. ⚠️ Security: x-forwarded-proto trusted without proxy validation
   Files: src/app/api/auth/token/route.ts:22-27, src/components/app-nav-bar/app-nav-bar.tsx:157, src/components/app-nav-bar/app-nav-bar.tsx:164, src/utils/auth/schemas/cadence-jwt-claims-schema.ts:6, src/route-handlers/auth-token/helpers/get-cookie-secure-attribute.ts:3-7, src/route-handlers/auth-token/set-auth-token.ts:34, src/route-handlers/auth-token/clear-auth-token.ts:19

   In `getCookieSecureAttribute`, the `x-forwarded-proto` header is unconditionally trusted to determine the cookie's `Secure` flag. If the app is deployed without a reverse proxy that strips or overwrites this header, a client can send `x-forwarded-proto: http` over an HTTPS connection to force `secure: false`, causing the auth cookie to be sent over plaintext connections. Conversely, `x-forwarded-proto: https` over HTTP would set `Secure` but the cookie would never be sent back.
   
   Consider either: (a) documenting this as a deployment requirement, (b) adding a config option to trust/ignore the header, or (c) defaulting to `secure: true` in production and only relaxing for development.

2. ⚠️ Performance: staleTime: 0 on useUserInfo causes excessive refetches
   Files: src/views/shared/hooks/use-user-info/use-user-info.ts:15, src/components/app-nav-bar/hooks/use-auth-lifecycle.ts:10, src/views/domain-workflows/domain-workflows.tsx:23, src/views/shared/hooks/use-user-info/use-user-info.ts:9

   The `useUserInfo` hook sets `staleTime: 0`, which overrides the project-wide default of `staleTime: Infinity` (set in `react-query-provider.tsx` specifically to prevent immediate client-side refetches in Next.js). Because `refetchOnWindowFocus` defaults to `true` in React Query, every tab focus event triggers a new `/api/auth/me` request. Additionally, every component mount treats cached data as stale and refetches. Since `AppNavBar` (always mounted at layout level) and `DomainWorkflows` both consume this hook, this creates unnecessary duplicate requests on page loads.
   
   Consider using a reasonable staleTime (e.g., 30-60 seconds) or `Infinity` to align with the project default. The expiry-based logout timer in `AppNavBar` already handles token expiration, and the explicit `refetch()` calls in `saveToken`/`logout` ensure freshness after mutations.

   Suggested fix:
   // use-user-info.ts
   export default function useUserInfo() {
     return useQuery<PublicAuthContext, RequestError>({
       queryKey: ['auth-me'],
       queryFn: async () => {
         const res = await request('/api/auth/me', { method: 'GET' });
         return res.json();
       },
       staleTime: 60_000, // 1 minute; mutations call refetch() explicitly
     });
   }

3. 💡 Edge Case: POST /api/auth/token sets cookie for expired JWTs
   Files: src/route-handlers/auth-token/set-auth-token.ts:21, src/route-handlers/auth-token/schemas/token-request-body-schema.ts:3, src/route-handlers/auth-token/set-auth-token.ts:19-33, src/route-handlers/auth-token/schemas/token-request-body-schema.ts:1-15

   The `setAuthToken` handler validates the JWT format (three dot-delimited base64url segments) but does not decode or check the `exp` claim. An already-expired token is accepted and persisted as an HttpOnly cookie. The user gets correct feedback (the subsequent `/api/auth/me` call returns `isValidToken: false`), but the round-trip is wasted and leaves a useless cookie on the client.
   
   Consider decoding and checking `exp` server-side before setting the cookie to provide immediate 400 feedback.

4. 💡 Quality: Deprecated `act` import from `react-dom/test-utils`
   Files: src/views/domain-workflows/__tests__/domain-workflows.test.tsx:4, src/views/domain-workflows/__tests__/domain-workflows.test.tsx:33-46

   `act` imported from `react-dom/test-utils` is deprecated in React 18 and removed in React 19. The recommended import is `import { act } from '@testing-library/react'` (which your test file already imports `render` and `screen` from `@/test-utils/rtl`, likely re-exporting `@testing-library/react`).
   
   Additionally, the try/catch-around-`act` pattern for asserting render errors is fragile — whether the error escapes `act` depends on React internals and can break across versions. The previous ErrorBoundary approach was more reliable and idiomatic for testing components that throw.

   Suggested fix:
   import { act, render, screen } from '@/test-utils/rtl';
   // or restore the ErrorBoundary pattern for error assertion

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@Assem-Uber
Copy link
Copy Markdown
Contributor

Guitar review comments are tracked in #1234

@Assem-Uber Assem-Uber self-requested a review April 7, 2026 11:44
@Assem-Uber Assem-Uber merged commit c51b4bb into cadence-workflow:master Apr 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants